﻿using FySystem.Exceptions;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace FySystem.Net.HttpAuth
{
    /// <summary>
    /// 1.如果使用自带的内存token认证，登录之后需要调用
    ///     SetSessionUser()方法来设置用户，
    ///     SetTokenExpireTime()方法来设置token失效时间，默认为2小时
    ///   使用简单，但每次重启token都会丢失；
    /// 2.如果使用的是自定义token认证，需要通过
    ///     CheckTokenRule()方法来初始化如何获取token，
    ///     UpdateTokenTimeRule()方法设置更新token时间的规则
    ///   每次登录之后自己实现过期逻辑，同时SetTokenExpireTime()方法也无效
    /// 3.必须至少要初始化一个openid来进行校验
    /// 4.每次接口调用时通过AuthTokenAndOpenID来进行校验，如果校验之后需要自动更新token时间
    /// </summary>
    public class HttpAuth
    {
        private static List<string> _openIDs = new List<string>();
        private static int _expireTime = 7200;                                                                            //token过期时间
        private static List<MemoryToken> _tokenCache = new List<MemoryToken>();                                           //内存形token校验

        #region 设置
        /// <summary>
        /// 使用token校验用户时，需要设定通过token获取用户和角色的规则
        /// </summary>
        public static Func<string, SessionUserBase> CheckTokenRule;

        /// <summary>
        /// 如果进行token校验后要更新token时间，则需要自定义实现该规则
        /// </summary>
        public static Action<string> UpdateTokenTimeRule;

        /// <summary>
        /// 添加验证OpenID，一些不需要登录的简单接口用
        /// </summary>
        /// <param name="openID"></param>
        /// <exception cref="NotImplementedException"></exception>
        public static void AddAuthOpenID(string openID)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(openID))
                    throw new Exception("openID为空~");
                if (openID.Length > 50)
                    throw new Exception("openID长度不能超过50~");

                lock (_openIDs)
                {
                    if (_openIDs.Contains(openID) == false)
                        _openIDs.Add(openID);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 设置token过期时间，单位s
        /// </summary>
        /// <param name="seconds"></param>
        public static void SetTokenExpireTime(int seconds)
        {
            try
            {
                if (seconds <= 0)
                    throw new Exception("token过期时间不能小于0");

                _expireTime = seconds;
            }
            catch (Exception)
            {
                throw;
            }
        }
        #endregion

        #region 校验
        /// <summary>
        /// 校验openid
        /// </summary>
        /// <param name="heads"></param>
        /// <exception cref="NotImplementedException"></exception>
        public static void AuthOpenID(KeyValuePair<string, StringValues>[] heads)
        {
            try
            {
                string openid = HttpHeadManager.Get(heads, "openid");
                if (_openIDs.Contains(openid) == false)
                    throw new Exception("OpenID校验失败~");
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 校验openid
        /// </summary>
        /// <param name="heads"></param>
        /// <returns></returns>
        public static FyRequestInfo AuthOpenIDWithCulture(KeyValuePair<string, StringValues>[] heads)
        {
            FyRequestInfo res = new FyRequestInfo();
            try
            {
                TokenAndOpenID tokenAndOpenID = HttpHeadManager.GetTokenAndOpenID(heads);
                if (_openIDs.Contains(tokenAndOpenID.OpenID) == false)
                    throw new Exception("OpenID校验失败~");

                res.Culture = tokenAndOpenID.Culture;
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 校验Token和openid
        /// </summary>
        /// <param name="heads"></param>
        /// <param name="useInterAuth">如果为true，使用自带的内存token校验机制</param>
        /// <returns></returns>
        public static FyRequestInfo AuthTokenAndOpenID(KeyValuePair<string, StringValues>[] heads, bool useInterAuth = false)
        {
            FyRequestInfo res = new FyRequestInfo();
            try
            {
                TokenAndOpenID tokenAndOpenID = HttpHeadManager.GetTokenAndOpenID(heads);
                if (string.IsNullOrWhiteSpace(tokenAndOpenID.OpenID))
                    throw new Exception("传入OpenID为空~");
                if (string.IsNullOrWhiteSpace(tokenAndOpenID.Token))
                    throw new Exception("传入Token为空~");
                if (_openIDs.Contains(tokenAndOpenID.OpenID) == false)
                    throw new Exception("OpenID校验失败~");

                res.Token = tokenAndOpenID.Token;
                res.Culture = tokenAndOpenID.Culture;

                if (useInterAuth == false)
                {
                    //如果是使用自定义方式校验
                    if (CheckTokenRule == null)
                        throw new Exception("未设置CheckTokenRule~");

                    res.User = CheckTokenRule(tokenAndOpenID.Token);
                    if (res.User == null)
                        throw new FyException("Token已失效~") { Code = -1 };

                    UpdateTokenTimeRule?.Invoke(tokenAndOpenID.Token);
                }
                else
                {
                    //如果是使用默认的方式校验
                    DateTime now = DateTime.Now;
                    MemoryToken mt = _tokenCache.Where(x => x.Token == tokenAndOpenID.Token).FirstOrDefault();
                    if (mt == null)
                        throw new FyException("Token已失效~") { Code = -1 };
                    if (mt.SessionTime.AddSeconds(_expireTime) < now)
                        throw new FyException("Token已失效~") { Code = -1 };

                    mt.SessionTime = now;
                    res.User = mt.User;
                }
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 如果是使用内部token校验，登录之后需要调用该方法，返回结果即为token
        /// </summary>
        /// <param name="user"></param>
        public static string SetSessionUser(SessionUserBase user)
        {
            string token = Guid.NewGuid().ToString();
            try
            {
                if (user == null)
                    throw new Exception("传入user为空~");

                user.token = token;
                lock (_tokenCache)
                {
                    MemoryToken mt = new MemoryToken();
                    mt.Token = token;
                    mt.User = user;
                    _tokenCache.Add(mt);

                    //查询并删除失效的token
                    DateTime now = DateTime.Now;
                    List<MemoryToken> expiredTokens = _tokenCache.Where(x => x.SessionTime < now.AddSeconds(0 - _expireTime)).ToList();
                    if (expiredTokens.Count > 0)
                    {
                        foreach(var item in expiredTokens)
                        {
                            _tokenCache.Remove(item);
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return token;
        }
        #endregion


    }
}
